// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>

#include <netinet/in.h>

#include <netlink/addr.h>
#include <netlink/cache.h>
#include <netlink/errno.h>
#include <netlink/netlink.h>
#include <netlink/object.h>
#include <netlink/socket.h>

#include <netlink/route/route.h>

#include <glog/logging.h>

#include <stout/error.hpp>
#include <stout/foreach.hpp>
#include <stout/none.hpp>
#include <stout/stringify.hpp>

#include "linux/routing/internal.hpp"
#include "linux/routing/route.hpp"

#include "linux/routing/link/link.hpp"

using std::string;
using std::vector;

namespace routing {
namespace route {

Try<vector<Rule>> table()
{
  Try<Netlink<struct nl_sock>> socket = routing::socket();
  if (socket.isError()) {
    return Error(socket.error());
  }

  // Dump all the routes (for IPv4) from kernel.
  struct nl_cache* c = nullptr;
  int error = rtnl_route_alloc_cache(socket.get().get(), AF_INET, 0, &c);
  if (error != 0) {
    return Error(nl_geterror(error));
  }

  Netlink<struct nl_cache> cache(c);

  vector<Rule> results;

  // Scan the routes and look for entries in the main routing table.
  for (struct nl_object* o = nl_cache_get_first(cache.get());
       o != nullptr; o = nl_cache_get_next(o)) {
    struct rtnl_route* route = (struct rtnl_route*) o;

    // TODO(jieyu): Currently, we assume each route in the routing
    // table has only one hop (which is true in most environments).
    if (rtnl_route_get_table(route) == RT_TABLE_MAIN &&
        rtnl_route_get_nnexthops(route) == 1) {
      CHECK_EQ(AF_INET, rtnl_route_get_family(route));

      // Get the destination IP network if exists.
      Option<net::IPNetwork> destination;
      struct nl_addr* dst = rtnl_route_get_dst(route);
      if (dst != nullptr && nl_addr_get_len(dst) != 0) {
        struct in_addr* addr = (struct in_addr*) nl_addr_get_binary_addr(dst);
        Try<net::IPNetwork> network = net::IPNetwork::create(
            net::IP(*addr),
            nl_addr_get_prefixlen(dst));

        if (network.isError()) {
          return Error(
              "Invalid IP network format from the routing table: " +
              network.error());
        }

        destination = network.get();
      }

      // Get the default gateway if exists.
      Option<net::IP> gateway;
      struct rtnl_nexthop* hop = rtnl_route_nexthop_n(route, 0);
      struct nl_addr* gw = rtnl_route_nh_get_gateway(CHECK_NOTNULL(hop));
      if (gw != nullptr && nl_addr_get_len(gw) != 0) {
        struct in_addr* addr = (struct in_addr*) nl_addr_get_binary_addr(gw);
        gateway = net::IP(*addr);
      }

      // Get the destination link.
      int index = rtnl_route_nh_get_ifindex(hop);
      Result<string> link = link::name(index);
      if (link.isError()) {
        return Error("Failed to get the link name: " + link.error());
      } else if (link.isNone()) {
        return Error("Link of index " + stringify(index) + " is not found");
      }

      results.push_back(Rule(destination, gateway, link.get()));
    }
  }

  return results;
}


Result<net::IP> defaultGateway()
{
  Try<vector<Rule>> rules = table();
  if (rules.isError()) {
    return Error("Failed to get the routing table: " + rules.error());
  }

  foreach (const Rule& rule, rules.get()) {
    if (rule.destination.isNone() && rule.gateway.isSome()) {
      return rule.gateway.get();
    }
  }

  return None();
}

} // namespace route {
} // namespace routing {
